package com.roamer.audition.server.service;

import com.roamer.audition.common.enums.StatusEnum;
import com.roamer.audition.common.request.AuditionRecordDTO;
import com.roamer.audition.common.request.AuditionRecordPageQuery;
import com.roamer.audition.common.request.AuditionRecordSimpleDTO;
import com.roamer.audition.common.request.AuditionRecordSimpleQuery;
import com.roamer.audition.common.response.AuditionRecordDetailVO;
import com.roamer.audition.common.response.AuditionRecordVO;
import com.roamer.audition.common.service.AuditionRecordService;
import com.roamer.audition.server.converter.AuditionRecordConverter;
import com.roamer.audition.server.entity.AuditionRecord;
import com.roamer.audition.server.entity.Company;
import com.roamer.audition.server.query.AuditionRecordPageQuerySpecification;
import com.roamer.audition.server.query.AuditionRecordSimpleQuerySpecification;
import com.roamer.audition.server.repository.AuditionRecordRepository;
import com.roamer.audition.server.repository.CompanyRepository;
import com.roamer.rest.exception.suppert.DataConflictException;
import com.roamer.rest.exception.suppert.DataNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 面试计划 业务处理实现
 *
 * @author roamer
 * @version V1.0
 * @date 2019-08-21 00:14
 */
@Service
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class AuditionRecordServiceImpl implements AuditionRecordService {

    private final AuditionRecordRepository auditionRecordRepository;
    private final AuditionRecordConverter auditionRecordConverter;
    private final CompanyRepository companyRepository;

    public AuditionRecordServiceImpl(AuditionRecordRepository auditionRecordRepository,
                                     AuditionRecordConverter auditionRecordConverter,
                                     CompanyRepository companyRepository) {
        this.auditionRecordRepository = auditionRecordRepository;
        this.auditionRecordConverter = auditionRecordConverter;
        this.companyRepository = companyRepository;
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ,
            rollbackFor = Exception.class)
    @Override
    public AuditionRecordVO createAuditionRecord(AuditionRecordSimpleDTO auditionRecordSimpleDTO)
            throws DataNotFoundException, DataConflictException {
        // 断言时间段内不存在其他安排
        assertNoAuditionRecordExistForPeriodOfTime(auditionRecordSimpleDTO.getBeginTime());
        Company company = getBaseCompany(auditionRecordSimpleDTO.getCompanyId());
        // 新增
        AuditionRecord auditionRecord = auditionRecordConverter.dtoToEntity(auditionRecordSimpleDTO);
        auditionRecord.setCompany(company);
        auditionRecord = auditionRecordRepository.save(auditionRecord);
        return auditionRecordConverter.entityToVO(auditionRecord);
    }

    /**
     * 断言时间段内不存在面试计划
     *
     * @param beginTime 开始时间
     *
     * @throws DataConflictException 时间段内已存在面试计划
     */
    private void assertNoAuditionRecordExistForPeriodOfTime(Long beginTime) throws DataConflictException {
        // 前后各两小时
        Calendar futureCalendar = new Calendar.Builder().setInstant(beginTime).build();
        futureCalendar.set(Calendar.HOUR_OF_DAY, futureCalendar.get(Calendar.HOUR_OF_DAY) - 2);
        Calendar pastCalendar = new Calendar.Builder().setInstant(beginTime).build();
        pastCalendar.set(Calendar.HOUR_OF_DAY, pastCalendar.get(Calendar.HOUR_OF_DAY) + 2);
        List<AuditionRecord> auditionRecordList = auditionRecordRepository.findAll(
                (root, query, builder) -> builder.between(root.get("beginTime"), futureCalendar.getTimeInMillis(),
                                                          pastCalendar.getTimeInMillis()));
        if (!auditionRecordList.isEmpty()) {
            throw new DataConflictException();
        }
    }

    /**
     * 获取公司
     *
     * @param companyId 公司ID
     *
     * @return {@code Company}
     *
     * @throws DataNotFoundException 公司不存在
     */
    private Company getBaseCompany(Long companyId) throws DataNotFoundException {
        // 获取公司
        Optional<Company> companyOptional = companyRepository.findById(companyId);
        companyOptional.orElseThrow(DataNotFoundException::new);
        return companyOptional.get();
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ,
            rollbackFor = Exception.class)
    @Override
    public AuditionRecordVO updateAuditionRecord(AuditionRecordDTO auditionRecordDTO) throws DataNotFoundException {
        Optional<AuditionRecord> auditionRecordOptional = auditionRecordRepository.findById(auditionRecordDTO.getId());
        auditionRecordOptional.orElseThrow(DataNotFoundException::new);
        auditionRecordConverter.copyNotNullPropertiesFromDTO(auditionRecordDTO, auditionRecordOptional.get());
        return auditionRecordConverter.entityToVO(auditionRecordOptional.get());
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ,
            rollbackFor = Exception.class)
    @Override
    public void deleteAuditionRecord(Long id) throws DataNotFoundException {
        Optional<AuditionRecord> auditionRecordOptional = auditionRecordRepository.findById(id);
        auditionRecordOptional.orElseThrow(DataNotFoundException::new);
        AuditionRecord auditionRecord = auditionRecordOptional.get();
        auditionRecordRepository.logicDelete(auditionRecord);
    }

    @Override
    public AuditionRecordVO getAuditionRecord(Long id) throws DataNotFoundException {
        Optional<AuditionRecord> auditionRecordOptional = auditionRecordRepository.findById(id);
        auditionRecordOptional.orElseThrow(DataNotFoundException::new);
        return auditionRecordConverter.entityToVO(auditionRecordOptional.get());
    }

    @Override
    public Page<AuditionRecordVO> getAuditionRecordPage(AuditionRecordPageQuery auditionRecordPageQuery) {
        AuditionRecordPageQuerySpecification specification = auditionRecordConverter.pageQueryToSpecification(
                auditionRecordPageQuery);
        Page<AuditionRecord> auditionRecordList = auditionRecordRepository.findAll(specification.toSpec(),
                specification.toPageable());
        return auditionRecordList.map(auditionRecordConverter::entityToVO);
    }

    @Override
    public List<AuditionRecordDetailVO> getAuditionRecordList(AuditionRecordSimpleQuery auditionRecordSimpleQuery) {
        AuditionRecordSimpleQuerySpecification specification = auditionRecordConverter.simpleQueryToSpecification(
                auditionRecordSimpleQuery);
        return auditionRecordRepository.findAll(specification.toSpec())
                                       .stream()
                                       .map(auditionRecordConverter::entityToDetailVO)
                                       .collect(Collectors.toList());
    }

    @Override
    public List<AuditionRecordDetailVO> getNearAuditionList(int minuteOffset) {
        LocalDateTime dateTime = LocalDateTime.now();
        Long beginTimeFrom = dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        dateTime.withMinute(minuteOffset);
        Long beginTimeTo = dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        AuditionRecordSimpleQuerySpecification query = new AuditionRecordSimpleQuerySpecification();
        query.setBeginTimeFrom(beginTimeFrom);
        query.setBeginTimeTo(beginTimeTo);
        query.setStatusList(Arrays.asList(StatusEnum.UN_STARTED.getValue()));
        return auditionRecordRepository.findAll(query.toSpec())
                                       .stream()
                                       .map(auditionRecordConverter::entityToDetailVO)
                                       .collect(Collectors.toList());
    }

}
